/**
* @file ddas_validator.C
* @author Max Rupplin
* @date 4-20-08
*/

#include <iostream>

#include <xercesc/util/PlatformUtils.hpp>

#include "config.h"
#include "util.h"
#include "sched_util.h"
#include "sched_msgs.h"
#include "validate_util.h"
#include "error_numbers.h"
#include "ddas_definitions.h"

#include "DDAS_XML_Handler.hpp"
#include "test_harness.hpp"

using std::string;
using std::vector;

using namespace std;

//function prototypes
int equal_rel_tolerance (double x, double y, double pct);


/**
* Initializes 'data' variable with information stored in 'result' (the client's result)
* @param result the client's result as a BOINC defined struct
* @param data a pointer to an arbitrary (DDAS defined) structure containing the data from the client's result
* @return returns 0 if successful, 'retval' (BOINC defined) otherwise
*/
int init_result(RESULT const & result, void*& data)
{
	// DDAS_XML_Handler handles XML parsing of result's from the client application
	DDAS_XML_Handler* xml_handler 		= new DDAS_XML_Handler();

	//Pointer to the initialized DDAS_Client_Result
	DDAS_Client_Result_Data* result_data;

	
	//FILE* points to the location of the client's output file on the server
	FILE* f;
  
	//path stores the actual location of the file retrieved by BOINC's 'get_output_file_path()' function
	string path;

	//retval stores the result(s) of various operations (pass/fail/error code)	
        int retval; 

	
	//BOINC API call - see http://boinc.berkeley.edu/trac/wiki/BackendUtilities
	retval = get_output_file_path(result, path);
        if (retval) 
	{
        	log_messages.printf
		(
            		SCHED_MSG_LOG::MSG_CRITICAL,
            		"[RESULT#%d %s] check_set: can't get output filename\n",
            		result.id, 
			result.name
        	);

		return retval;
	}
        

	//parse the client's output file, stick it in 'result_data' and cast it to void*
	try
	{
		result_data 	= xml_handler->parseResultData( result );

		data		= ( (void*)result_data );
		
		delete xml_handler;
	}
	catch( char* e )
	{
                log_messages.printf
                (
                        SCHED_MSG_LOG::MSG_CRITICAL,
                        "Exception thrown parsing [RESULT#%d %s] w/ output filename %s, in ddas_validator.C.\n",
                        result.id,
                        result.name,
			path.c_str()
                );

                return retval;		
	}

	return 0; 
}


/**
* Compares two output files, 'data1' and 'data2' to see if they match. This specific implementation checks several things:
* 1) Do the DDAS_CLIENT_RESULT_DATA have the same number of data rows?
* 2) Do the DDAS_CLIENT_RESULT_DATA have the same definition numbers? 
* 3) Do the DDAS_CLIENT_RESULT_DATA sum of squared (SSE) agree within 1%?
* If any of these is false, 'match' is set to false and the function returns -1
*
* @param r1 the first result for a given workunit
* @param data1 the data set of the first result
* @param r2 the second result for a given workunit
* @param data2 the data set of the second result 
* @param match boolean variable holding the result of the comparison
* @return returns 0 if successful, -1 otherwise
*/
int compare_results(RESULT & r1, void* data1, RESULT const& r2, void* data2, bool& match)
{
 	DDAS_Client_Result_Data* crd1 = (DDAS_Client_Result_Data*)data1; 
	DDAS_Client_Result_Data* crd2 = (DDAS_Client_Result_Data*)data2;

	match=true;

	//do result data have same number of data_rows
	if(crd1->getRowCount() != crd2->getRowCount())
	{
		match=false;
		return -1;
	}


	//does either DDAS_CLIENT_RESULT_DATA contain duplicate definition numbers
	//UNIMPL'D


	//do both results have the same definition numbers 
        for(int i=0; i<crd1->getRowCount(); i++)
        {
                for(int j=0; j<crd2->getRowCount(); j++)
                {
			if( strcmp(crd1->getDefinitionNumber(i), crd2->getDefinitionNumber(j))==0 ) 
			{
				break;
			}

			if( j == (crd2->getRowCount()-1) ) 
			{
				match=false;
				return -1;
			}
                }
        }

	//are SSEs for equivalent within some threshold
        for(int i=0; i<crd1->getRowCount(); i++)
        {
                for(int j=0; j<crd2->getRowCount(); j++)
                {
                        if( strcmp(crd1->getDefinitionNumber(i), crd2->getDefinitionNumber(j))==0 )
			{
				if(! equal_rel_tolerance(crd1->getSSE(i), crd2->getSSE(j), SSE_RELATIVE_TOLERANCE) )
				{
					match=false;
					return -1;
				}
			}
                }
        }


	return 0;
}

/**
* A simple clean up function to prevent memory leaks. cleanup_result is called after any pertinent work on the results and/or their data.
*
* @param result the result in question
* @param data the result's data
* @return returns 0
*/
int cleanup_result(RESULT const& result, void* data)
{
	if(data) 
	{
		delete ( DDAS_Client_Result_Data* ) data;
	}
	
	return 0;
}


/**
* Grants credit for a set of results. Uses 'median_mean_credit()' a BOINC defined function - see http://boinc.berkeley.edu/trac/wiki/BackendUtilities
*
* @param results a vector of results for a given workunit
* @return returns if N==1, return that result's claimed credit, if N==2, return min of claimed credits, if N>2, toss out high and low claimed credit, and return the average of the rest 
*/
double compute_granted_credit(std::vector<RESULT>& results)
{
	return median_mean_credit(results);
}

/**
* A non-BOINC helper function that determines if two doubles are equivalent within some percentage
*
* @param x first double to be compared
* @param relativeTo second double to be compared against 'x'
* @param pct the percentage threshold 
* @return returns 1 if 'x' is within 'pct' percentage of 'relativeTo', 0 otherwise 
*/
int equal_rel_tolerance (double x, double relativeTo, double pct)
{
	int return_value = 0;
	double min = 0.0;
	double max = 0.0;

	min = relativeTo * (1.0 - pct);
	max = relativeTo * (1.0 + pct);

	return_value = (x > min) && (x < max) ? 1 : 0;	

	return return_value;
}
